#include <utl/vector.h>
#include <gfx/vk/core.h>
#include <gfx/vk/helper.h>
#include <utl/logger.h>
#include <common_header.h>
#include <utl/hash_map.h>

/*

        u32 d_ext_count{};
        utl::vector<VkExtensionProperties> d_ext_propertys{};
        vkEnumerateDeviceExtensionProperties(globle.vk_physical_device, nullptr, &d_ext_count, nullptr);
        d_ext_propertys.resize(d_ext_count);
        vkEnumerateDeviceExtensionProperties(globle.vk_physical_device, nullptr, &d_ext_count, d_ext_propertys.data());

        for (auto &item : d_ext_propertys)
        {
            utl::logger().critical(item.extensionName);
        }

        u32 d_layer_count{};
        utl::vector<VkLayerProperties> d_layer_propertys{};
        vkEnumerateDeviceLayerProperties(globle.vk_physical_device,&d_layer_count,nullptr);
        d_layer_propertys.resize(d_layer_count);
        vkEnumerateDeviceLayerProperties(globle.vk_physical_device,&d_layer_count,d_layer_propertys.data());
        for (auto &item : d_layer_propertys)
        {
            utl::logger().info(item.layerName);
        }


        {
            uint32_t ext_count{};
            utl::vector<VkExtensionProperties> ext_propertys{};
            vkEnumerateInstanceExtensionProperties(nullptr, &ext_count, nullptr);
            ext_propertys.resize(ext_count);
            vkEnumerateInstanceExtensionProperties(nullptr, &ext_count, ext_propertys.data());

            for (auto &item : ext_propertys)
            {
                utl::logger().info(item.extensionName);
            }
        }
        {
            uint32_t layer_count{};
            utl::vector<VkLayerProperties> layer_propertys{};
            vkEnumerateInstanceLayerProperties(&layer_count, nullptr);
            layer_propertys.resize(layer_count);
            vkEnumerateInstanceLayerProperties(&layer_count, layer_propertys.data());
            for (auto &item : layer_propertys)
            {
                utl::logger().warn(item.layerName);
                utl::logger().info(item.description);
            }
        }
        {
            utl::logger().info("=========all physicals=========");
            for (auto &item : physicals)
            {
                VkPhysicalDeviceProperties prop{};
                vkGetPhysicalDeviceProperties(item, &prop);
                utl::logger().info(prop.deviceName);
            }
            utl::logger().info("=========all physicals=========");
        }
        {
            u32 family_count{};
            utl::vector<VkQueueFamilyProperties> familys{};
            vkGetPhysicalDeviceQueueFamilyProperties(globle.vk_physical_device, &family_count, nullptr);
            familys.resize(family_count);
            vkGetPhysicalDeviceQueueFamilyProperties(globle.vk_physical_device, &family_count, familys.data());

            for(auto& item : familys)
            {
                utl::logger().info("family_flag:{:b},count:{}", item.queueFlags, item.queueCount);
            }
        }
*/

namespace primal
{
    namespace gfx
    {

        struct
        {
            VkInstance vk_instance;
            //VkDebugReportCallbackEXT debug_report;
            VkDebugUtilsMessengerEXT debug_utils;
            VkPhysicalDevice vk_physical_device;
            VkDeviceClass vk_device_class;
            VkQueue vk_graphics_queue;
            VkQueue vk_transfer_queue;
            //VolkDeviceTable device_table;
            VkDescriptorPool vk_desc_pool;
            u32 default_memory_type_index;
            u32 upload_memory_type_index;
            utl::hash_map<VkBuffer, VkDeviceMemory> memory_cache;
            bool is_init;
        } globle{};

        VkResult VkDeviceClass::CreateCommandPool(const VkCommandPoolCreateInfo* pCreateInfo, VkCommandPool* pCommandPool)
        {
            return this->vkCreateCommandPool(_dev, pCreateInfo, alloc_cbk(), pCommandPool);
        }
        VkResult VkDeviceClass::AllocateCommandBuffers(const VkCommandBufferAllocateInfo* pAllocateInfo, VkCommandBuffer* pCommandBuffers)
        {
            return this->vkAllocateCommandBuffers(_dev, pAllocateInfo, pCommandBuffers);
        }
        void VkDeviceClass::GetDeviceQueue(uint32_t queueFamilyIndex, uint32_t queueIndex, VkQueue* pQueue)
        {
            this->vkGetDeviceQueue(_dev, queueFamilyIndex, queueIndex, pQueue);
        }
        VkResult VkDeviceClass::CreateSwapchainKHR(const VkSwapchainCreateInfoKHR* pCreateInfo, VkSwapchainKHR* pSwapchain)
        {
            return this->vkCreateSwapchainKHR(_dev, pCreateInfo, alloc_cbk(), pSwapchain);
        }
        VkResult VkDeviceClass::GetSwapchainImagesKHR(VkSwapchainKHR swapchain, uint32_t* pSwapchainImageCount, VkImage* pSwapchainImages)
        {
            return this->vkGetSwapchainImagesKHR(_dev, swapchain, pSwapchainImageCount, pSwapchainImages);
        }
        VkResult VkDeviceClass::CreateRenderPass(const VkRenderPassCreateInfo* pCreateInfo, VkRenderPass* pRenderPass)
        {
            return this->vkCreateRenderPass(_dev, pCreateInfo, alloc_cbk(), pRenderPass);
        }
        VkResult VkDeviceClass::CreatePipelineLayout(const VkPipelineLayoutCreateInfo* pCreateInfo, VkPipelineLayout* pPipelineLayout)
        {
            return this->vkCreatePipelineLayout(_dev, pCreateInfo, alloc_cbk(), pPipelineLayout);
        }
        VkResult VkDeviceClass::CreateGraphicsPipelines(VkPipelineCache pipelineCache, uint32_t createInfoCount, const VkGraphicsPipelineCreateInfo* pCreateInfos, VkPipeline* pPipelines)
        {
            return this->vkCreateGraphicsPipelines(_dev, pipelineCache, createInfoCount, pCreateInfos, alloc_cbk(), pPipelines);
        }
        VkResult VkDeviceClass::CreateImageView(const VkImageViewCreateInfo* pCreateInfo, VkImageView* pView)
        {
            return this->vkCreateImageView(_dev, pCreateInfo, alloc_cbk(), pView);
        }
        VkResult VkDeviceClass::CreateFramebuffer(const VkFramebufferCreateInfo* pCreateInfo, VkFramebuffer* pFramebuffer)
        {
            return this->vkCreateFramebuffer(_dev, pCreateInfo, alloc_cbk(), pFramebuffer);
        }
        VkResult VkDeviceClass::AcquireNextImageKHR(VkSwapchainKHR swapchain, uint64_t timeout, VkSemaphore semaphore, VkFence fence, uint32_t* pImageIndex)
        {
            return this->vkAcquireNextImageKHR(_dev, swapchain, timeout, semaphore, fence, pImageIndex);
        }
        VkResult VkDeviceClass::QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* pPresentInfo)
        {
            return this->vkQueuePresentKHR(queue, pPresentInfo);
        }
        VkResult VkDeviceClass::CreateShaderModule(const VkShaderModuleCreateInfo* pCreateInfo, VkShaderModule* pShaderModule)
        {
            return this->vkCreateShaderModule(_dev, pCreateInfo, alloc_cbk(), pShaderModule);
        }
        VkResult VkDeviceClass::CreateSemaphore(VkSemaphore* pSemaphore)
        {
            return this->vkCreateSemaphore(_dev, &vkx::def.semaphore_create_info, alloc_cbk(), pSemaphore);
        }
        VkResult VkDeviceClass::CreateFence(VkFence* pFence)
        {
            return this->vkCreateFence(_dev, &vkx::def.fence_create_info, alloc_cbk(), pFence);
        }
        VkResult VkDeviceClass::WaitForFences(uint32_t fenceCount, const VkFence* pFences, VkBool32 waitAll, uint64_t timeout)
        {
            return this->vkWaitForFences(_dev, fenceCount, pFences, waitAll, timeout);
        }
        VkResult VkDeviceClass::WaitForFences(const VkFence* pFences)
        {
            return this->vkWaitForFences(_dev, 1u, pFences, VK_TRUE, UINT64_MAX);
        }
        VkResult VkDeviceClass::ResetFences(uint32_t fenceCount, const VkFence* pFences)
        {
            return this->vkResetFences(_dev, fenceCount, pFences);
        }
        VkResult VkDeviceClass::ResetFences(const VkFence* pFences)
        {
            return this->vkResetFences(_dev, 1u, pFences);
        }
        VkResult VkDeviceClass::ResetCommandBuffer(VkCommandBuffer commandBuffer, VkCommandBufferResetFlags flags)
        {
            return this->vkResetCommandBuffer(commandBuffer, flags);
        }
        VkResult VkDeviceClass::ResetCommandPool(VkCommandPool commandPool, VkCommandPoolResetFlags flags)
        {
            return this->vkResetCommandPool(_dev, commandPool, flags);
        }
        PFN_vkVoidFunction VkDeviceClass::GetProcAddr(const char* name)
        {
            return vkGetDeviceProcAddr(_dev, name);
        }
        VkResult VkDeviceClass::CreateDescriptorPool(const VkDescriptorPoolCreateInfo* pCreateInfo, VkDescriptorPool* pDescriptorPool)
        {
            return this->vkCreateDescriptorPool(_dev, pCreateInfo, alloc_cbk(), pDescriptorPool);
        }
        VkResult VkDeviceClass::CreateBuffer(const VkBufferCreateInfo* pCreateInfo, VkBuffer* pBuffer)
        {
            return this->vkCreateBuffer(_dev, pCreateInfo, alloc_cbk(), pBuffer);
        }
        VkResult VkDeviceClass::CreateBufferEx(VkDeviceSize size, VkBufferUsageFlags usage, MemoryPropertyTypes type, VkBuffer& buf)
        {
            VkBufferCreateInfo bufferInfo{};
            bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
            bufferInfo.size = size;
            bufferInfo.usage = usage;
            bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
            auto res = CreateBuffer(&bufferInfo, &buf);

            VkMemoryRequirements memRequirements;
            vkGetBufferMemoryRequirements(_dev, buf, &memRequirements);

            VkResult create_memory_res{ VK_ERROR_UNKNOWN };
            VkDeviceMemory entity_memory{};

            switch(type)
            {
            case primal::gfx::Default:
                create_memory_res = this->AllocateDefaultMemory(entity_memory, memRequirements.size);
                break;
            case primal::gfx::Upload:
                create_memory_res = this->AllocateUploadMemory(entity_memory, memRequirements.size);
                break;
            }

            VkResult bind_res = this->BindBufferMemory(buf, entity_memory, 0ull);

            globle.memory_cache[buf] = entity_memory;

            return (res == VK_SUCCESS && create_memory_res == VK_SUCCESS && bind_res == VK_SUCCESS) ? VkResult::VK_SUCCESS : VkResult::VK_ERROR_UNKNOWN;
        }
        void VkDeviceClass::DestoryBuffer(VkBuffer buf)
        {
            this->vkDestroyBuffer(_dev, buf, alloc_cbk());
        }
        VkResult VkDeviceClass::AllocateeMemory(const VkMemoryAllocateInfo* pAllocateInfo, VkDeviceMemory* pMemory)
        {
            return this->vkAllocateMemory(_dev, pAllocateInfo, alloc_cbk(), pMemory);
        }
        VkResult VkDeviceClass::AllocateDefaultMemory(VkDeviceMemory& mem, VkDeviceSize size)
        {
            VkMemoryAllocateInfo inf{};
            inf.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
            inf.allocationSize = size;
            inf.memoryTypeIndex = globle.default_memory_type_index;

            return this->AllocateeMemory(&inf, &mem);
        }
        VkResult VkDeviceClass::AllocateUploadMemory(VkDeviceMemory& mem, VkDeviceSize size)
        {
            VkMemoryAllocateInfo inf{};
            inf.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
            inf.allocationSize = size;
            inf.memoryTypeIndex = globle.upload_memory_type_index;

            return this->AllocateeMemory(&inf, &mem);
        }
        void VkDeviceClass::FreeMemory(VkDeviceMemory memory)
        {
            this->vkFreeMemory(_dev, memory, alloc_cbk());
        }
        VkResult VkDeviceClass::MapMemory(VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void** ppData)
        {
            return this->vkMapMemory(_dev, memory, offset, size, flags, ppData);
        }
        void VkDeviceClass::UnmapMemory(VkDeviceMemory mem)
        {
            this->vkUnmapMemory(_dev, mem);
        }
        VkResult VkDeviceClass::BindBufferMemory(VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset)
        {
            return this->vkBindBufferMemory(_dev, buffer, memory, memoryOffset);
        }
        VkResult VkDeviceClass::CreateDescriptorSetLayout(const VkDescriptorSetLayoutCreateInfo* pCreateInfo, VkDescriptorSetLayout* pSetLayout)
        {
            return this->vkCreateDescriptorSetLayout(_dev, pCreateInfo, alloc_cbk(), pSetLayout);
        }
        VkResult VkDeviceClass::AllocateDescriptorSets(const VkDescriptorSetAllocateInfo* pAllocateInfo, VkDescriptorSet* pDescriptorSets)
        {
            return this->vkAllocateDescriptorSets(_dev, pAllocateInfo, pDescriptorSets);
        }
        void VkDeviceClass::UpdateDescriptorSets(uint32_t descriptorWriteCount, const VkWriteDescriptorSet* pDescriptorWrites, uint32_t descriptorCopyCount, const VkCopyDescriptorSet* pDescriptorCopies)
        {
            this->vkUpdateDescriptorSets(_dev, descriptorWriteCount, pDescriptorWrites, descriptorCopyCount, pDescriptorCopies);
        }
    } // namespace gfx

} // namespace primal

VkAllocationCallbacks *primal::gfx::alloc_cbk()
{
    return nullptr;
}

VkInstance primal::gfx::vk_instance()
{
    return globle.vk_instance;
}

VkPhysicalDevice primal::gfx::phy()
{
    return globle.vk_physical_device;
}

primal::gfx::VkDeviceClass& primal::gfx::dev()
{
    return globle.vk_device_class;
}

VkQueue primal::gfx::graphics_queue()
{
    return globle.vk_graphics_queue;
}

VkQueue primal::gfx::transfer_queue()
{
    return globle.vk_transfer_queue;
}

void primal::gfx::GetPhysicalDeviceSurfaceFormats(VkSurfaceKHR surface)
{
    u32 count{};
    VkSurfaceFormatKHR formats[50]{};
    vkGetPhysicalDeviceSurfaceFormatsKHR(globle.vk_physical_device,surface,&count,nullptr);
    vkGetPhysicalDeviceSurfaceFormatsKHR(globle.vk_physical_device,surface,&count,formats);
    utl::logger().info(count);
}

VkDescriptorPool primal::gfx::desc_pool()
{
    return VkDescriptorPool();
}

VkBool32 VkDebugUtilCallback(
    VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
    VkDebugUtilsMessageTypeFlagsEXT messageTypes,
    const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData,
    void *pUserData)
{
    spdlog::level::level_enum msg_level{};

    switch(messageSeverity)
    {
    case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:
        msg_level = spdlog::level::debug;
        break;
    case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:
        msg_level = spdlog::level::info;
        break;
    case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
        msg_level = spdlog::level::warn;
        break;
    case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
        msg_level = spdlog::level::critical;
        break;
    }
    utl::logger().log(msg_level, "=========================Begin=========================");
    utl::logger().log(msg_level, pCallbackData->pMessage);
    utl::logger().log(msg_level, "==========================End==========================\n");
    return msg_level == spdlog::level::critical ? VK_TRUE : VK_FALSE;
}

void memory_debug()
{
    VkPhysicalDeviceMemoryProperties mem_prop{};
    vkGetPhysicalDeviceMemoryProperties(primal::gfx::globle.vk_physical_device, &mem_prop);

    utl::logger().info("VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT           :{:b}", VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
    utl::logger().info("VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT           :{:b}", VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
    utl::logger().info("VK_MEMORY_PROPERTY_HOST_COHERENT_BIT          :{:b}", VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
    utl::logger().info("VK_MEMORY_PROPERTY_HOST_CACHED_BIT            :{:b}", VK_MEMORY_PROPERTY_HOST_CACHED_BIT);
    utl::logger().info("VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT       :{:b}", VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT);
    utl::logger().info("VK_MEMORY_PROPERTY_PROTECTED_BIT              :{:b}", VK_MEMORY_PROPERTY_PROTECTED_BIT);
    utl::logger().info("VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD    :{:b}", VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD);
    utl::logger().info("VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD    :{:b}", VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD);
    utl::logger().info("VK_MEMORY_PROPERTY_RDMA_CAPABLE_BIT_NV        :{:b}", VK_MEMORY_PROPERTY_RDMA_CAPABLE_BIT_NV);

    for(u32 i{}; i < mem_prop.memoryTypeCount; ++i)
    {
        utl::logger().debug("{:b}", mem_prop.memoryTypes[i].propertyFlags);
    }
}

bool primal::gfx::initialize()
{
    if (globle.is_init)
    {
        return globle.is_init;
    }

    {
        volkInitialize();

        vkx::instance_create_info ins_info{};
        vkCreateInstance(ins_info.init(), alloc_cbk(), &globle.vk_instance);
        volkLoadInstanceOnly(globle.vk_instance);

#ifndef NDEBUG
        vkx::debug_create_info debug_util_info{};
        //VKASSERT(vkCreateDebugReportCallbackEXT(globle.vk_instance, debug_create_info.as_report(VkDebugCallback), alloc_cbk(), &globle.debug_report));
        VKASSERT(vkCreateDebugUtilsMessengerEXT(globle.vk_instance, debug_util_info.as_utils(VkDebugUtilCallback), alloc_cbk(), &globle.debug_utils));
#endif // !NDEBUG

        utl::vector<VkPhysicalDevice> physicals{};
        {
            uint32_t physical_count{};
            vkEnumeratePhysicalDevices(globle.vk_instance, &physical_count, nullptr);
            physicals.resize(physical_count);
            vkEnumeratePhysicalDevices(globle.vk_instance, &physical_count, physicals.data());
        }
        globle.vk_physical_device = physicals[0];

        vkx::device_create_info<1u> dci{};
        float device_queues[max_queue_count]{};
        dci.init_queue(0u, default_queue_family_index, device_queues, max_queue_count);

        VKASSERT(vkCreateDevice(globle.vk_physical_device, dci.init(), alloc_cbk(), &globle.vk_device_class._dev));

        volkLoadDeviceTable(&globle.vk_device_class, globle.vk_device_class._dev);

        dev().GetDeviceQueue(default_queue_family_index, 0u, &globle.vk_graphics_queue);
        dev().GetDeviceQueue(default_queue_family_index, 0u, &globle.vk_transfer_queue);

        //TODO:
        globle.default_memory_type_index = 0u;
        globle.upload_memory_type_index = 1u;
    }

    globle.is_init = true;
    return globle.is_init;
}
//
//VolkDeviceTable &primal::gfx::device_fun()
//{
//    return globle.device_table;
//}